/*! Responsive 1.0.6
 * 2014-2015 SpryMedia Ltd - datatables.net/license
 */

/**
 * @summary     Responsive
 * @description Responsive tables plug-in for DataTables
 * @version     1.0.6
 * @file        dataTables.responsive.js
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
 * @contact     www.sprymedia.co.uk/contact
 * @copyright   Copyright 2014-2015 SpryMedia Ltd.
 *
 * This source file is free software, available under the following license:
 *   MIT license - http://datatables.net/license/mit
 *
 * This source file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 *
 * For details please refer to: http://www.datatables.net
 */

(function (window, document, undefined) {


    var factory = function ($, DataTable) {
        "use strict";

        /**
         * Responsive is a plug-in for the DataTables library that makes use of
         * DataTables' ability to change the visibility of columns, changing the
         * visibility of columns so the displayed columns fit into the table container.
         * The end result is that complex tables will be dynamically adjusted to fit
         * into the viewport, be it on a desktop, tablet or mobile browser.
         *
         * Responsive for DataTables has two modes of operation, which can used
         * individually or combined:
         *
         * * Class name based control - columns assigned class names that match the
         *   breakpoint logic can be shown / hidden as required for each breakpoint.
         * * Automatic control - columns are automatically hidden when there is no
         *   room left to display them. Columns removed from the right.
         *
         * In additional to column visibility control, Responsive also has built into
         * options to use DataTables' child row display to show / hide the information
         * from the table that has been hidden. There are also two modes of operation
         * for this child row display:
         *
         * * Inline - when the control element that the user can use to show / hide
         *   child rows is displayed inside the first column of the table.
         * * Column - where a whole column is dedicated to be the show / hide control.
         *
         * Initialisation of Responsive is performed by:
         *
         * * Adding the class `responsive` or `dt-responsive` to the table. In this case
         *   Responsive will automatically be initialised with the default configuration
         *   options when the DataTable is created.
         * * Using the `responsive` option in the DataTables configuration options. This
         *   can also be used to specify the configuration options, or simply set to
         *   `true` to use the defaults.
         *
         *  @class
         *  @param {object} settings DataTables settings object for the host table
         *  @param {object} [opts] Configuration options
         *  @requires jQuery 1.7+
         *  @requires DataTables 1.10.1+
         *
         *  @example
         *      $('#example').DataTable( {
         *        responsive: true
         *      } );
         *    } );
         */
        var Responsive = function (settings, opts) {
            // Sanity check that we are using DataTables 1.10 or newer
            if (!DataTable.versionCheck || !DataTable.versionCheck('1.10.1')) {
                throw 'DataTables Responsive requires DataTables 1.10.1 or newer';
            }

            this.s = {
                dt: new DataTable.Api(settings),
                columns: []
            };

            // Check if responsive has already been initialised on this table
            if (this.s.dt.settings()[0].responsive) {
                return;
            }

            // details is an object, but for simplicity the user can give it as a string
            if (opts && typeof opts.details === 'string') {
                opts.details = {type: opts.details};
            }

            this.c = $.extend(true, {}, Responsive.defaults, DataTable.defaults.responsive, opts);
            settings.responsive = this;
            this._constructor();
        };

        Responsive.prototype = {
            /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
             * Constructor
             */

            /**
             * Initialise the Responsive instance
             *
             * @private
             */
            _constructor: function ()
            {
                var that = this;
                var dt = this.s.dt;

                dt.settings()[0]._responsive = this;

                // Use DataTables' private throttle function to avoid processor thrashing
                $(window).on('resize.dtr orientationchange.dtr', dt.settings()[0].oApi._fnThrottle(function () {
                    that._resize();
                }));

                // Destroy event handler
                dt.on('destroy.dtr', function () {
                    $(window).off('resize.dtr orientationchange.dtr draw.dtr');
                });

                // Reorder the breakpoints array here in case they have been added out
                // of order
                this.c.breakpoints.sort(function (a, b) {
                    return a.width < b.width ? 1 :
                            a.width > b.width ? -1 : 0;
                });

                // Determine which columns are already hidden, and should therefore
                // remain hidden. todo - should this be done? See thread 22677
                //
                // this.s.alwaysHidden = dt.columns(':hidden').indexes();

                this._classLogic();
                this._resizeAuto();

                // Details handler
                var details = this.c.details;
                if (details.type) {
                    that._detailsInit();
                    this._detailsVis();

                    dt.on('column-visibility.dtr', function () {
                        that._detailsVis();
                    });

                    // Redraw the details box on each draw. This is used until
                    // DataTables implements a native `updated` event for rows
                    dt.on('draw.dtr', function () {
                        dt.rows({page: 'current'}).iterator('row', function (settings, idx) {
                            var row = dt.row(idx);

                            if (row.child.isShown()) {
                                var info = that.c.details.renderer(dt, idx);
                                row.child(info, 'child').show();
                            }
                        });
                    });

                    $(dt.table().node()).addClass('dtr-' + details.type);
                }

                // First pass - draw the table for the current viewport size
                this._resize();
            },

            /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
             * Private methods
             */

            /**
             * Calculate the visibility for the columns in a table for a given
             * breakpoint. The result is pre-determined based on the class logic if
             * class names are used to control all columns, but the width of the table
             * is also used if there are columns which are to be automatically shown
             * and hidden.
             *
             * @param  {string} breakpoint Breakpoint name to use for the calculation
             * @return {array} Array of boolean values initiating the visibility of each
             *   column.
             *  @private
             */
            _columnsVisiblity: function (breakpoint)
            {
                var dt = this.s.dt;
                var columns = this.s.columns;
                var i, ien;

                // Class logic - determine which columns are in this breakpoint based
                // on the classes. If no class control (i.e. `auto`) then `-` is used
                // to indicate this to the rest of the function
                var display = $.map(columns, function (col) {
                    return col.auto && col.minWidth === null ?
                            false :
                            col.auto === true ?
                            '-' :
                            $.inArray(breakpoint, col.includeIn) !== -1;
                });

                // Auto column control - first pass: how much width is taken by the
                // ones that must be included from the non-auto columns
                var requiredWidth = 0;
                for (i = 0, ien = display.length; i < ien; i++) {
                    if (display[i] === true) {
                        requiredWidth += columns[i].minWidth;
                    }
                }

                // Second pass, use up any remaining width for other columns. For
                // scrolling tables we need to subtract the width of the scrollbar. It
                // may not be requires which makes this sub-optimal, but it would
                // require another full redraw to make complete use of those extra few
                // pixels
                var scrolling = dt.settings()[0].oScroll;
                var bar = scrolling.sY || scrolling.sX ? scrolling.iBarWidth : 0;
                var widthAvailable = dt.table().container().offsetWidth - bar;
                var usedWidth = widthAvailable - requiredWidth;

                // Control column needs to always be included. This makes it sub-
                // optimal in terms of using the available with, but to stop layout
                // thrashing or overflow. Also we need to account for the control column
                // width first so we know how much width is available for the other
                // columns, since the control column might not be the first one shown
                for (i = 0, ien = display.length; i < ien; i++) {
                    if (columns[i].control) {
                        usedWidth -= columns[i].minWidth;
                    }
                }

                // Allow columns to be shown (counting from the left) until we run out
                // of room
                var empty = false;
                for (i = 0, ien = display.length; i < ien; i++) {
                    if (display[i] === '-' && !columns[i].control) {
                        // Once we've found a column that won't fit we don't let any
                        // others display either, or columns might disappear in the
                        // middle of the table
                        if (empty || usedWidth - columns[i].minWidth < 0) {
                            empty = true;
                            display[i] = false;
                        } else {
                            display[i] = true;
                        }

                        usedWidth -= columns[i].minWidth;
                    }
                }

                // Determine if the 'control' column should be shown (if there is one).
                // This is the case when there is a hidden column (that is not the
                // control column). The two loops look inefficient here, but they are
                // trivial and will fly through. We need to know the outcome from the
                // first , before the action in the second can be taken
                var showControl = false;

                for (i = 0, ien = columns.length; i < ien; i++) {
                    if (!columns[i].control && !columns[i].never && !display[i]) {
                        showControl = true;
                        break;
                    }
                }

                for (i = 0, ien = columns.length; i < ien; i++) {
                    if (columns[i].control) {
                        display[i] = showControl;
                    }
                }

                // Finally we need to make sure that there is at least one column that
                // is visible
                if ($.inArray(true, display) === -1) {
                    display[0] = true;
                }

                return display;
            },

            /**
             * Create the internal `columns` array with information about the columns
             * for the table. This includes determining which breakpoints the column
             * will appear in, based upon class names in the column, which makes up the
             * vast majority of this method.
             *
             * @private
             */
            _classLogic: function ()
            {
                var that = this;
                var calc = {};
                var breakpoints = this.c.breakpoints;
                var columns = this.s.dt.columns().eq(0).map(function (i) {
                    var className = this.column(i).header().className;

                    return {
                        className: className,
                        includeIn: [],
                        auto: false,
                        control: false,
                        never: className.match(/\bnever\b/) ? true : false
                    };
                });

                // Simply add a breakpoint to `includeIn` array, ensuring that there are
                // no duplicates
                var add = function (colIdx, name) {
                    var includeIn = columns[ colIdx ].includeIn;

                    if ($.inArray(name, includeIn) === -1) {
                        includeIn.push(name);
                    }
                };

                var column = function (colIdx, name, operator, matched) {
                    var size, i, ien;

                    if (!operator) {
                        columns[ colIdx ].includeIn.push(name);
                    } else if (operator === 'max-') {
                        // Add this breakpoint and all smaller
                        size = that._find(name).width;

                        for (i = 0, ien = breakpoints.length; i < ien; i++) {
                            if (breakpoints[i].width <= size) {
                                add(colIdx, breakpoints[i].name);
                            }
                        }
                    } else if (operator === 'min-') {
                        // Add this breakpoint and all larger
                        size = that._find(name).width;

                        for (i = 0, ien = breakpoints.length; i < ien; i++) {
                            if (breakpoints[i].width >= size) {
                                add(colIdx, breakpoints[i].name);
                            }
                        }
                    } else if (operator === 'not-') {
                        // Add all but this breakpoint (xxx need extra information)

                        for (i = 0, ien = breakpoints.length; i < ien; i++) {
                            if (breakpoints[i].name.indexOf(matched) === -1) {
                                add(colIdx, breakpoints[i].name);
                            }
                        }
                    }
                };

                // Loop over each column and determine if it has a responsive control
                // class
                columns.each(function (col, i) {
                    var classNames = col.className.split(' ');
                    var hasClass = false;

                    // Split the class name up so multiple rules can be applied if needed
                    for (var k = 0, ken = classNames.length; k < ken; k++) {
                        var className = $.trim(classNames[k]);

                        if (className === 'all') {
                            // Include in all
                            hasClass = true;
                            col.includeIn = $.map(breakpoints, function (a) {
                                return a.name;
                            });
                            return;
                        } else if (className === 'none' || className === 'never') {
                            // Include in none (default) and no auto
                            hasClass = true;
                            return;
                        } else if (className === 'control') {
                            // Special column that is only visible, when one of the other
                            // columns is hidden. This is used for the details control
                            hasClass = true;
                            col.control = true;
                            return;
                        }

                        $.each(breakpoints, function (j, breakpoint) {
                            // Does this column have a class that matches this breakpoint?
                            var brokenPoint = breakpoint.name.split('-');
                            var re = new RegExp('(min\\-|max\\-|not\\-)?(' + brokenPoint[0] + ')(\\-[_a-zA-Z0-9])?');
                            var match = className.match(re);

                            if (match) {
                                hasClass = true;

                                if (match[2] === brokenPoint[0] && match[3] === '-' + brokenPoint[1]) {
                                    // Class name matches breakpoint name fully
                                    column(i, breakpoint.name, match[1], match[2] + match[3]);
                                } else if (match[2] === brokenPoint[0] && !match[3]) {
                                    // Class name matched primary breakpoint name with no qualifier
                                    column(i, breakpoint.name, match[1], match[2]);
                                }
                            }
                        });
                    }

                    // If there was no control class, then automatic sizing is used
                    if (!hasClass) {
                        col.auto = true;
                    }
                });

                this.s.columns = columns;
            },

            /**
             * Initialisation for the details handler
             *
             * @private
             */
            _detailsInit: function ()
            {
                var that = this;
                var dt = this.s.dt;
                var details = this.c.details;

                // The inline type always uses the first child as the target
                if (details.type === 'inline') {
                    details.target = 'td:first-child';
                }

                // type.target can be a string jQuery selector or a column index
                var target = details.target;
                var selector = typeof target === 'string' ? target : 'td';

                // Click handler to show / hide the details rows when they are available
                $(dt.table().body()).on('click', selector, function (e) {
                    // If the table is not collapsed (i.e. there is no hidden columns)
                    // then take no action
                    if (!$(dt.table().node()).hasClass('collapsed')) {
                        return;
                    }

                    // Check that the row is actually a DataTable's controlled node
                    if (!dt.row($(this).closest('tr')).length) {
                        return;
                    }

                    // For column index, we determine if we should act or not in the
                    // handler - otherwise it is already okay
                    if (typeof target === 'number') {
                        var targetIdx = target < 0 ?
                                dt.columns().eq(0).length + target :
                                target;

                        if (dt.cell(this).index().column !== targetIdx) {
                            return;
                        }
                    }

                    // $().closest() includes itself in its check
                    var row = dt.row($(this).closest('tr'));

                    if (row.child.isShown()) {
                        row.child(false);
                        $(row.node()).removeClass('parent');
                    } else {
                        var info = that.c.details.renderer(dt, row[0]);
                        row.child(info, 'child').show();
                        $(row.node()).addClass('parent');
                    }
                });
            },

            /**
             * Update the child rows in the table whenever the column visibility changes
             *
             * @private
             */
            _detailsVis: function ()
            {
                var that = this;
                var dt = this.s.dt;

                // Find how many columns are hidden
                var hiddenColumns = dt.columns().indexes().filter(function (idx) {
                    var col = dt.column(idx);

                    if (col.visible()) {
                        return null;
                    }

                    // Only counts as hidden if it doesn't have the `never` class
                    return $(col.header()).hasClass('never') ? null : idx;
                });
                var haveHidden = true;

                if (hiddenColumns.length === 0 || (hiddenColumns.length === 1 && this.s.columns[ hiddenColumns[0] ].control)) {
                    haveHidden = false;
                }

                if (haveHidden) {
                    // Show all existing child rows
                    dt.rows({page: 'current'}).eq(0).each(function (idx) {
                        var row = dt.row(idx);

                        if (row.child()) {
                            var info = that.c.details.renderer(dt, row[0]);

                            // The renderer can return false to have no child row
                            if (info === false) {
                                row.child.hide();
                            } else {
                                row.child(info, 'child').show();
                            }
                        }
                    });
                } else {
                    // Hide all existing child rows
                    dt.rows({page: 'current'}).eq(0).each(function (idx) {
                        dt.row(idx).child.hide();
                    });
                }
            },

            /**
             * Find a breakpoint object from a name
             * @param  {string} name Breakpoint name to find
             * @return {object}      Breakpoint description object
             */
            _find: function (name)
            {
                var breakpoints = this.c.breakpoints;

                for (var i = 0, ien = breakpoints.length; i < ien; i++) {
                    if (breakpoints[i].name === name) {
                        return breakpoints[i];
                    }
                }
            },

            /**
             * Alter the table display for a resized viewport. This involves first
             * determining what breakpoint the window currently is in, getting the
             * column visibilities to apply and then setting them.
             *
             * @private
             */
            _resize: function ()
            {
                var dt = this.s.dt;
                var width = $(window).width();
                var breakpoints = this.c.breakpoints;
                var breakpoint = breakpoints[0].name;
                var columns = this.s.columns;
                var i, ien;

                // Determine what breakpoint we are currently at
                for (i = breakpoints.length - 1; i >= 0; i--) {
                    if (width <= breakpoints[i].width) {
                        breakpoint = breakpoints[i].name;
                        break;
                    }
                }

                // Show the columns for that break point
                var columnsVis = this._columnsVisiblity(breakpoint);

                // Set the class before the column visibility is changed so event
                // listeners know what the state is. Need to determine if there are
                // any columns that are not visible but can be shown
                var collapsedClass = false;
                for (i = 0, ien = columns.length; i < ien; i++) {
                    if (columnsVis[i] === false && !columns[i].never) {
                        collapsedClass = true;
                        break;
                    }
                }

                $(dt.table().node()).toggleClass('collapsed', collapsedClass);

                dt.columns().eq(0).each(function (colIdx, i) {
                    dt.column(colIdx).visible(columnsVis[i]);
                });
            },

            /**
             * Determine the width of each column in the table so the auto column hiding
             * has that information to work with. This method is never going to be 100%
             * perfect since column widths can change slightly per page, but without
             * seriously compromising performance this is quite effective.
             *
             * @private
             */
            _resizeAuto: function ()
            {
                var dt = this.s.dt;
                var columns = this.s.columns;

                // Are we allowed to do auto sizing?
                if (!this.c.auto) {
                    return;
                }

                // Are there any columns that actually need auto-sizing, or do they all
                // have classes defined
                if ($.inArray(true, $.map(columns, function (c) {
                    return c.auto;
                })) === -1) {
                    return;
                }

                // Clone the table with the current data in it
                var tableWidth = dt.table().node().offsetWidth;
                var columnWidths = dt.columns;
                var clonedTable = dt.table().node().cloneNode(false);
                var clonedHeader = $(dt.table().header().cloneNode(false)).appendTo(clonedTable);
                var clonedBody = $(dt.table().body().cloneNode(false)).appendTo(clonedTable);

                $(dt.table().footer()).clone(false).appendTo(clonedTable);

                // This is a bit slow, but we need to get a clone of each row that
                // includes all columns. As such, try to do this as little as possible.
                dt.rows({page: 'current'}).indexes().flatten().each(function (idx) {
                    var clone = dt.row(idx).node().cloneNode(true);

                    if (dt.columns(':hidden').flatten().length) {
                        $(clone).append(dt.cells(idx, ':hidden').nodes().to$().clone());
                    }

                    $(clone).appendTo(clonedBody);
                });

                var cells = dt.columns().header().to$().clone(false);
                $('<tr/>')
                        .append(cells)
                        .appendTo(clonedHeader);

                // In the inline case extra padding is applied to the first column to
                // give space for the show / hide icon. We need to use this in the
                // calculation
                if (this.c.details.type === 'inline') {
                    $(clonedTable).addClass('dtr-inline collapsed');
                }

                var inserted = $('<div/>')
                        .css({
                            width: 1,
                            height: 1,
                            overflow: 'hidden'
                        })
                        .append(clonedTable);

                // Remove columns which are not to be included
                inserted.find('th.never, td.never').remove();

                inserted.insertBefore(dt.table().node());

                // The cloned header now contains the smallest that each column can be
                dt.columns().eq(0).each(function (idx) {
                    columns[idx].minWidth = cells[ idx ].offsetWidth || 0;
                });

                inserted.remove();
            }
        };


        /**
         * List of default breakpoints. Each item in the array is an object with two
         * properties:
         *
         * * `name` - the breakpoint name.
         * * `width` - the breakpoint width
         *
         * @name Responsive.breakpoints
         * @static
         */
        Responsive.breakpoints = [
            {name: 'desktop', width: Infinity},
            {name: 'tablet-l', width: 1024},
            {name: 'tablet-p', width: 768},
            {name: 'mobile-l', width: 480},
            {name: 'mobile-p', width: 320}
        ];


        /**
         * Responsive default settings for initialisation
         *
         * @namespace
         * @name Responsive.defaults
         * @static
         */
        Responsive.defaults = {
            /**
             * List of breakpoints for the instance. Note that this means that each
             * instance can have its own breakpoints. Additionally, the breakpoints
             * cannot be changed once an instance has been creased.
             *
             * @type {Array}
             * @default Takes the value of `Responsive.breakpoints`
             */
            breakpoints: Responsive.breakpoints,

            /**
             * Enable / disable auto hiding calculations. It can help to increase
             * performance slightly if you disable this option, but all columns would
             * need to have breakpoint classes assigned to them
             *
             * @type {Boolean}
             * @default  `true`
             */
            auto: true,

            /**
             * Details control. If given as a string value, the `type` property of the
             * default object is set to that value, and the defaults used for the rest
             * of the object - this is for ease of implementation.
             *
             * The object consists of the following properties:
             *
             * * `renderer` - function that is called for display of the child row data.
             *   The default function will show the data from the hidden columns
             * * `target` - Used as the selector for what objects to attach the child
             *   open / close to
             * * `type` - `false` to disable the details display, `inline` or `column`
             *   for the two control types
             *
             * @type {Object|string}
             */
            details: {
                renderer: function (api, rowIdx) {
                    var data = api.cells(rowIdx, ':hidden').eq(0).map(function (cell) {
                        var header = $(api.column(cell.column).header());
                        var idx = api.cell(cell).index();

                        if (header.hasClass('control') || header.hasClass('never')) {
                            return '';
                        }

                        // Use a non-public DT API method to render the data for display
                        // This needs to be updated when DT adds a suitable method for
                        // this type of data retrieval
                        var dtPrivate = api.settings()[0];
                        var cellData = dtPrivate.oApi._fnGetCellData(
                                dtPrivate, idx.row, idx.column, 'display'
                                );
                        var title = header.text();
                        if (title) {
                            title = title + ':';
                        }

                        return '<li data-dtr-index="' + idx.column + '">' +
                                '<span class="dtr-title">' +
                                title +
                                '</span> ' +
                                '<span class="dtr-data">' +
                                cellData +
                                '</span>' +
                                '</li>';
                    }).toArray().join('');

                    return data ?
                            $('<ul data-dtr-index="' + rowIdx + '"/>').append(data) :
                            false;
                },

                target: 0,

                type: 'inline'
            }
        };


        /*
         * API
         */
        var Api = $.fn.dataTable.Api;

// Doesn't do anything - work around for a bug in DT... Not documented
        Api.register('responsive()', function () {
            return this;
        });

        Api.register('responsive.index()', function (li) {
            li = $(li);

            return {
                column: li.data('dtr-index'),
                row: li.parent().data('dtr-index')
            };
        });

        Api.register('responsive.rebuild()', function () {
            return this.iterator('table', function (ctx) {
                if (ctx._responsive) {
                    ctx._responsive._classLogic();
                }
            });
        });

        Api.register('responsive.recalc()', function () {
            return this.iterator('table', function (ctx) {
                if (ctx._responsive) {
                    ctx._responsive._resizeAuto();
                    ctx._responsive._resize();
                }
            });
        });


        /**
         * Version information
         *
         * @name Responsive.version
         * @static
         */
        Responsive.version = '1.0.6';


        $.fn.dataTable.Responsive = Responsive;
        $.fn.DataTable.Responsive = Responsive;

// Attach a listener to the document which listens for DataTables initialisation
// events so we can automatically initialise
        $(document).on('init.dt.dtr', function (e, settings, json) {
            if (e.namespace !== 'dt') {
                return;
            }

            if ($(settings.nTable).hasClass('responsive') ||
                    $(settings.nTable).hasClass('dt-responsive') ||
                    settings.oInit.responsive ||
                    DataTable.defaults.responsive
                    ) {
                var init = settings.oInit.responsive;

                if (init !== false) {
                    new Responsive(settings, $.isPlainObject(init) ? init : {});
                }
            }
        });

        return Responsive;
    }; // /factory


// Define as an AMD module if possible
    if (typeof define === 'function' && define.amd) {
        define(['jquery', 'datatables'], factory);
    } else if (typeof exports === 'object') {
        // Node/CommonJS
        factory(require('jquery'), require('datatables'));
    } else if (jQuery && !jQuery.fn.dataTable.Responsive) {
        // Otherwise simply initialise as normal, stopping multiple evaluation
        factory(jQuery, jQuery.fn.dataTable);
    }


})(window, document);
